/**
 * Copyright 2014 Michail Basios
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;

/**
 * Main class of the generator
 * 
 * @author <a href="http://www.basios.com" target="blank">Michail Basios</a>
 * @version 1.0 Build February 17, 2014.
 * 
 */
public class Main {

	/**
	 * Global variables which are used from other classes as well
	 */
	public static String folderName;
	public static String folderPath;

	public static void main(String[] args) {

		System.out.println("#################################################");
		System.out
				.println("\n   Code Generator v.1.1 (Automatic generation) \n");
		System.out
				.println("#################################################\n");

		String inputFile = null;

		if (args.length > 0)
			inputFile = args[0];
		else {
			inputFile = "//home//maba18//workspace7//TreesProject//src//Main.java";
		}

		File f = new File(inputFile);

		folderPath = f.getParent();

		File f1 = new File(folderPath);
		folderPath = f1.getAbsolutePath();
		folderName = f1.getName();

		InputStream is = System.in;

		if (inputFile != null) {
			try {
				is = new FileInputStream(inputFile);
			} catch (FileNotFoundException e) {

				e.printStackTrace();
			}
		}
		/**
		 * The following line refer to the process of reading the main source
		 * file of the project and extracting information about the collections
		 * being used.
		 */
		ANTLRInputStream input = null;
		try {
			input = new ANTLRInputStream(is);
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		JavaLexer lexer = new JavaLexer(input);
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		JavaParser parser = new JavaParser(tokens);

		ParseTree tree = parser.compilationUnit(); // parse

		ParseTreeWalker walker = new ParseTreeWalker(); // create standard
														// walker

		/**
		 * This class is used for parsing the Main java file and find the
		 * objects saved on collections. For example: If there is a collection
		 * arrayList which contains an object Demo, it will find the object Demo
		 * and will save it for later inspection. In case, the Demo object
		 * contains the annotation @Table and @Key, it will trigger a generation
		 * of a new class.
		 * 
		 */
		ExtractObjectsListener extractor = new ExtractObjectsListener(parser);

		walker.walk(extractor, tree); // initiate walk of tree with listener

		/**
		 * 
		 * This following code refers to the automatic code generation for the
		 * most used field of the class
		 */

		/**
		 * This stores the counter of each variable
		 */
		HashMap<String, Integer> counterTable = new HashMap<String, Integer>();
		/**
		 * This does the mapping between the string name of the variable and its
		 * ctx Contex, for example getId -> ctx. This is very useful for
		 * obtaining the proper information about the string
		 */
		HashMap<String, JavaParser.FieldDeclarationContext> counterTableMapping = new HashMap<String, JavaParser.FieldDeclarationContext>();

		HashSet<JavaParser.FieldDeclarationContext> identifiedVariables = new HashSet<>();

		/**
		 * Contains information about the file the variable came from
		 */
		HashMap<JavaParser.FieldDeclarationContext, String> allVariablesInFile = ClassInformationListener
				.getVariableBelongsInFile();
		/**
		 * Contains information about the class where the variable is used
		 */
		HashMap<JavaParser.FieldDeclarationContext, String> allVariablesInClass = ClassInformationListener
				.getVariableBelongsInClass();

		HashMap<String, JavaParser.FieldDeclarationContext> finalVariables = new HashMap<>();

		if (!allVariablesInClass.isEmpty()) {
			// System.out.println("All variables: " + allVariablesInFile);

			for (JavaParser.FieldDeclarationContext ctx : allVariablesInFile
					.keySet()) {
				// System.out.println("Pedio: " + ctx.getText());
				// System.out.println("FilePathName: "
				// + allVariablesInFile.get(ctx));
				// System.out.println(ctx.getStart().getText());
				// System.out.println(Database.primitiveToObjects.get(ctx
				// .getStart().getText()));
				// System.out.println(ctx.getChild(1).getText());
				// System.out.println(ctx.getParent().getParent().getChild(0)
				// .getText());

				String finalName = getIdentifierName(ctx, ctx.getChild(1)
						.getText(), ctx.getParent().getParent().getChild(0)
						.getText());
				// System.out.println("Field Name: " + finalName);

				if (!counterTable.containsKey(finalName.trim())) {
					counterTable.put(finalName.trim(), 0);
					counterTableMapping.put(finalName.trim(), ctx);
				} else {
					int value = counterTable.get(finalName.trim());
					counterTable.put(finalName.trim(), value + 1);

				}

			}

			// System.out.println(counterTable);

			// identifiedVariables = ClassInformationListener.getVariables();

			// System.out.println("testttttttttttttt");
			// System.out.println(counterTable);
			for (int i = 0; i < tokens.size(); i++) {
				// System.out.println(tokens.get(i).getText());
				if (counterTable.containsKey(tokens.get(i).getText())) {
					int value = counterTable.get(tokens.get(i).getText());
					counterTable.put(tokens.get(i).getText(), value + 1);
				}
			}
			// System.out.println(counterTable);
			Map.Entry<String, Integer> maxEntry = null;

			for (Map.Entry<String, Integer> entry : counterTable.entrySet()) {
				if (maxEntry == null
						|| entry.getValue().compareTo(maxEntry.getValue()) > 0) {
					maxEntry = entry;
				}
			}

			// System.out.println("Max entry is: " + maxEntry);

			TemplateGenerator generator = new TemplateGenerator();

			JavaParser.FieldDeclarationContext ctxTemp = counterTableMapping
					.get(maxEntry.getKey());
			String className = allVariablesInClass.get(ctxTemp);
			String keyName = ctxTemp.getChild(1).getText();

			String keyName1 = maxEntry.getKey();
			String variablePrivateOrPublic = ctxTemp.getParent().getParent()
					.getChild(0).getText();
			String keyObject = Database.primitiveToObjects.get(ctxTemp
					.getStart().getText());
			String keyPrimitive = ctxTemp.getStart().getText();
			String accessName = getAccessName(ctxTemp, keyName,
					variablePrivateOrPublic);

			// System.out.println("className: " + className + " keyName: "
			// + keyName + " accessName: " + accessName + " keyObject: "
			// + keyObject + " keyPrimitive: " + keyPrimitive);

			generator.generateArrayList(className, keyName, accessName,
					keyObject, keyPrimitive);

			// System.out.println("FilePathName: " +
			// allVariablesInFile.get(ctxTemp));
			// System.out.println("aaaaaaaaaaaa: "
			// + extractor.getObjectDeclarationInformationMap()
			// .get(allVariablesInFile.get(ctxTemp)).getText());
			extractor.replaceAllInMain(
					extractor.getObjectDeclarationInformationMap().get(
							allVariablesInFile.get(ctxTemp)), className);
			System.out.println(counterTable);

		}

		// for (String s : counterTable.keySet()) {
		// System.out.println("Key String Name: " + s);
		//
		// System.out.println("Counter: " + counterTable.get(s));
		//
		// System.out.println("To ctx: "
		// + counterTableMapping.get(s).getText());
		//
		// JavaParser.FieldDeclarationContext ctx1 = counterTableMapping
		// .get(s);
		//
		// System.out.println("The file path name: "
		// + allVariablesInFile.get(counterTableMapping.get(s)));
		// if (counterTable.get(s) > 0) {
		// // GenerateTabularArrayList.generate(args)
		// System.out.println("The file path name: "
		// + allVariablesInFile.get(counterTableMapping.get(s)));
		// System.out.println("The class name is: "
		// + allVariablesInClass.get(ctx1));
		// TemplateGenerator generator1 = new TemplateGenerator();
		// // generator.generateArrayList(allVariablesInClass.get(ctx1),
		// // keyName, accessName, keyObject, keyPrimitive);
		//
		// }

		// for (String s : identifiedVariables) {
		// System.out.println(s);
		// }
		// for (int i = 0; i < tokens.size(); i++) {
		// // System.out.println("1..... :" + tokens.get(i).getText());
		//
		// String trimmedVariableName = tokens.get(i).getText().trim();
		// if (identifiedVariables.contains(trimmedVariableName)) {
		// System.out.println("EMperiexetai :" + tokens.get(i).getText());
		// if (!counterTable.containsKey(trimmedVariableName)) {
		// counterTable.put(trimmedVariableName, 1);
		// } else {
		// int value = counterTable.get(trimmedVariableName);
		// counterTable.put(trimmedVariableName, value + 1);
		// }
		// }
		// }

		/**
		 * Save the altered input file
		 */
		// System.out.println(extractor.getRewriter().getText());

		// inputFile=inputFile.replaceAll(".java", "1.java");

		File file = new File(inputFile);
		File dir = new File(new File(inputFile).getParent().toString()
				+ "/temp");
		// System.out.println("To dir einai: " + dir);
		if (!dir.exists()) {

			dir.mkdir();
		}
		File fileMain = new File(new File(inputFile).getParent() + "/temp/"
				+ new File(inputFile).getName());
		// System.out.println("The folder path is: " + folderPath + "\n A"
		// + new File(new File(inputFile).getParent()));
		System.out
				.println("The new generated Main file is called: " + fileMain);

		if (!fileMain.exists()) {
			try {
				fileMain.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		FileWriter fw = null;
		try {
			fw = new FileWriter(fileMain.getAbsoluteFile());
		} catch (IOException e) {
			e.printStackTrace();
		}
		BufferedWriter bw = new BufferedWriter(fw);
		try {
			bw.write(extractor.getRewriter().getText());
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			ArrayList<String> filesInsideDirectory = new ArrayList<String>();
			ArrayList<String> generatedFilesInsideDirectory = new ArrayList<String>();
			HashMap<String, String> testNames = new HashMap<String, String>();
			//
			// System.out.println("Gia na doume afto: "
			// + file.getParentFile().toString());

			testNames = testFilesInsideDirectory(testNames, file
					.getParentFile().toString());
			// System.out.println("The initial files for compilation are:");
			// for (String s : testNames.values()) {
			// System.out.println(s);
			// }
			// System.out.println("To parentFIlename tou file einai: "
			// + file.getParentFile());
			// System.out.println("To parentFIlename tou file einai: "
			// + fileMain.getParentFile().getParentFile());
			testNames = testFilesInsideDirectory(testNames, fileMain
					.getParentFile().getParentFile().toString()
					+ "/temp");

			System.out
					.println("The new files that are going to be compiled are: ");
			for (String s : testNames.values()) {

				if (s.contains("temp")) {
					System.out.println(s);
				}
			}

			// filesInsideDirectory = listFilesInsideDirectory(file
			// .getParentFile().toString());
			// generatedFilesInsideDirectory = listFilesInsideDirectory(file
			// .getParentFile().toString() + "/temp");

			String[] tempList = new String[testNames.size()];

			tempList = testNames.values().toArray(tempList);

			// System.out.println(file.getParentFile().toString() +
			// "/Demo.java");
			// System.out.println(new File(folderPath).getParentFile());
			String stringaki = new File(folderPath).getParentFile().toString();
			// System.out.println("TO folder path gia to compile prin bei einai: "+fileMain.getParentFile().getParentFile().getParentFile()
			// .toString());
			Compile.main(tempList, stringaki);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static HashMap<String, String> testFilesInsideDirectory(
			HashMap<String, String> testNames, String path) {
		// Directory path here
		// System.out.println("To path einai: " + path);
		ArrayList<String> filenames = new ArrayList<String>();
		// HashMap<String, String> testNames = new HashMap<String, String>();
		String files;
		File folder = new File(path);
		File[] listOfFiles = folder.listFiles();

		for (int i = 0; i < listOfFiles.length; i++) {

			if (listOfFiles[i].isFile()) {
				files = listOfFiles[i].getName();
				if (files.endsWith(".java")) {

					if (!testNames.containsValue(files)) {
						testNames.put(files, path + "/" + files);
					} else {
						testNames.put(files, path + "/temp/" + files);
					}
					filenames.add(path + "/" + files);
					// System.out.println(files);

				}
			}
		}

		return testNames;
	}

	/**
	 * Gets the name the field is used in the source file
	 * 
	 * @param ctx
	 * @param variableName
	 * @param variablePrivateOrPublic
	 * @return
	 */
	public static String getIdentifierName(
			JavaParser.FieldDeclarationContext ctx, String variableName,
			String variablePrivateOrPublic) {
		String variableType = ctx.getStart().getText(); // String or int or
		// System.out.println("Variable Type is: "+variableType); // Double

		String accessName = variableName;

		String identifiedName = variableName;

		if (!variablePrivateOrPublic.equals("private")
				&& !variablePrivateOrPublic.equals("public")) {
			variablePrivateOrPublic = "";
		}
		if (variablePrivateOrPublic.equals("private")) {
			String CapitalisedVariableName = variableName.substring(0, 1)
					.toUpperCase() + variableName.substring(1);
			accessName = "get" + CapitalisedVariableName + "()";
			identifiedName = "get" + CapitalisedVariableName;
		}
		return identifiedName;
	}

	public static String getAccessName(JavaParser.FieldDeclarationContext ctx,

	String variableName, String variablePrivateOrPublic) {
		String variableType = ctx.getStart().getText(); // String or int or
		// System.out.println("Variable Type is: "+variableType); // Double

		String accessName = variableName;

		String identifiedName = variableName;

		if (!variablePrivateOrPublic.equals("private")
				&& !variablePrivateOrPublic.equals("public")) {
			variablePrivateOrPublic = "";
		}
		if (variablePrivateOrPublic.equals("private")) {
			String CapitalisedVariableName = variableName.substring(0, 1)
					.toUpperCase() + variableName.substring(1);
			accessName = "get" + CapitalisedVariableName + "()";
			identifiedName = "get" + CapitalisedVariableName;
		}
		return accessName;
	}

}
